import { ts, SwitchStatement, rule, BreakStatement, is, CallExpression, BinaryExpression } from "qvog-dsl";

const SEVERITY = 2;
const DOC_PATH = 'docs/use-isnan-check.md';
const DESCRIPTION = 'Array prototype method cannot find NaN.';

function isSpecificId(node: ts.Node, name: string): boolean {
    return ts.isIdentifier(node) && node.text === name;
}

function isSpecificMemberAccess(node: ts.Node, objectName: string, propertyName: string): boolean {
    return (ts.isPropertyAccessExpression(node) &&
        ts.isIdentifier(node.expression) && node.expression.text === objectName &&
        ts.isIdentifier(node.name) && node.name.text === propertyName) ||
        (ts.isElementAccessExpression(node) &&
            ts.isIdentifier(node.expression) && node.expression.text === objectName &&
            ts.isStringLiteral(node.argumentExpression) && node.argumentExpression.text === propertyName);
}

function isNaNIdentifier(node: ts.Node): boolean {
    if (ts.isParenthesizedExpression(node)) {
        let express = node.expression;
        if (ts.isBinaryExpression(express)) {
            return isNaNIdentifier(express.right);
        }
        return isNaNIdentifier(express);
    }

    return isSpecificId(node, 'NaN') || isSpecificMemberAccess(node, 'Number', 'NaN');
}

function containsNaN(node: ts.Node): boolean {
  if (isNaNIdentifier(node)) {
    return true;
  }
  if (ts.isParenthesizedExpression(node)) {
    return containsNaN(node.expression);
  }
  if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.CommaToken) {
    return containsNaN(node.right);
  }
  if (ts.isCommaListExpression(node)) {
    return node.elements.some(element => containsNaN(element));
  }
  return false;
}

export default rule()
    .match(CallExpression)
    .when((node): boolean => {
        const expression = node.expression;
        const callee = ts.isParenthesizedExpression(expression) ? expression.expression : expression;
        if (ts.isPropertyAccessExpression(callee) || ts.isElementAccessExpression(callee)) {
            const methodName = ts.isPropertyAccessExpression(callee) ? callee.name.text :
                ts.isElementAccessExpression(callee) && ts.isStringLiteral(callee.argumentExpression) ?
                    callee.argumentExpression.text : null;
            if (methodName && ['indexOf', 'lastIndexOf'].includes(methodName) &&
                (node.arguments.length > 0 && node.arguments.length <= 2 && isNaNIdentifier(node.arguments[0]))) {
                for (const arg of node.arguments) {
                    if (containsNaN(arg)) {
                        return true;
                    }
                }
            }
        }
        return false;
    })
    .report({
        severity: SEVERITY,
        description: DESCRIPTION,
        docPath: DOC_PATH,
    });
